/*********************************************
 * Student    : Teodoro Cipresso
 * Course     : CS286
 * Professor  : Vidya Setlur
 * Assignment : Term Project (BlueTanks)
 *********************************************/
package edu.sjsu.cs286.teodoro.cipresso.sprites;

import java.io.IOException;
import java.util.Random;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;

import edu.sjsu.cs286.teodoro.cipresso.bluetooth.BlueTankMessageHandler;
import edu.sjsu.cs286.teodoro.cipresso.playfield.BlueTankGameCanvas;
import edu.sjsu.cs286.teodoro.cipresso.sound.BlueTankSoundHolder;
import edu.sjsu.cs286.teodoro.cipresso.wizard.BlueTankGameContainer;

/**
 * This class represents a ownerTank controlled by a player in the game. The
 * state of this object is shared between players to display the ownerTank in
 * the appropriate state on the opponents game client.
 * 
 * @author Teodoro Cipresso with lots of help from,
 *         http://developers.sun.com/techtopics/mobility/midp/articles/game/
 * 
 */
public class BlueTankVehicleSprite extends Sprite implements IBlueTankSprite {

	// true if last key pressed made tank rotate
	private boolean lastMoveWasRotate;

	// tank name constant
	private String tankName;

	// normal tank image
	private Image tankImage;

	// tank firing image
	private Image tankFireImage;

	// tank cannon sound
	private Player cannonSnd;

	// delta amount of last move
	private int lastMoveDelta;

	// color of tank using RGB
	private int tankRGB;

	// one of 16 vectors representing the current direction the tanks cannon is
	// pointing
	private int cannonDirection = 0;

	// current armor points of tank, inited to max
	private int armorPoints = MAX_ARMOR_POINTS;

	// instance of message handler used to exchange messages with the client
	private BlueTankMessageHandler btMsgHndlr;

	// instance of utility class to traverse vectors and maintain state
	private BlueTankVectorTraversal btVectorMove;

	// the parent game midlet
	private BlueTankGameContainer container;

	// the parent game canvas
	private BlueTankGameCanvas btCanvas;

	// current projectile instance, will be null if tank not firing
	private BlueTankProjectileSprite projectile = null;

	// inidicator if tank may fire (projectile may still be null when initiating
	// firing)
	private boolean cannonReady = true;

	// indicator whether we can issue another drive request
	private boolean driveReady = true;

	// indicator whether we can issue another rotate request
	private boolean rotateReady = true;

	// whether image of tank firing weapon is in use
	private boolean firingImage = false;

	// pseudo-random number generator
	private Random random = new Random();

	/**
	 * Default constructor. Creates a new instance of the tank sprite, and
	 * initializes it to the initial state. Initial state includes tank specific
	 * sound, images and initial position and direction vector.
	 * 
	 * @param image,
	 *            the tank specific image to use
	 * 
	 * @param rgb,
	 *            the numeric representation of the tank color
	 * 
	 * @param name,
	 *            the tank name constant from IBlueTankSprite
	 * 
	 * @param frameWidth,
	 *            the width of frames in image param
	 * 
	 * @param frameHeight,
	 *            the Height of frames in image param
	 * 
	 */
	public BlueTankVehicleSprite(Image image, int rgb, String name,
			int frameWidth, int frameHeight, BlueTankGameContainer parent) {
		super(image, frameWidth, frameHeight);
		defineReferencePixel(frameWidth / 2, frameHeight / 2);
		//
		container = parent;
		tankImage = image;
		tankRGB = rgb;
		tankName = name;
		//
		cannonSnd = BlueTankSoundHolder.getTankCannonPlayerByName(tankName);
		tankFireImage = BlueTankImageHolder.getTankFireImageByName(tankName);
		//
		btMsgHndlr = container.getBtManager().getBtMessageHandler();
		btCanvas = container.getBtCanvas();
		//
		btVectorMove = new BlueTankVectorTraversal(this);
	} // BlueTankVehicleSprite()

	/**
	 * This method is called when the user presses the firing key. The process
	 * of firing the cannon involves signaling intent to the peer and receiving
	 * a time in the future when the actual firing will occur.
	 */
	public void tryFireCannon() {
		// disallow firing if we're destroyed
		if (armorPoints == 0) {
			return;
		}
		// can only fire if we're not already in the process
		if (projectile == null && cannonReady) {
			btMsgHndlr.sendTankFireCommitRequest(System.currentTimeMillis());
			cannonReady = false;
		}
	} // tryFireCannon()

	/**
	 * Once we have the OK from the peer to fire the cannon (we got the time to
	 * fire back), wait till that time and then fire the cannon.
	 */
	public void doFireCannon(long time) {
		//
		try {
			// Wait for target commit time
			while (System.currentTimeMillis() < time || (projectile != null)) {
				Thread.yield();
			}
			// restart sound
			cannonSnd.stop();
			cannonSnd.setMediaTime(0);
			cannonSnd.start();
			// new projectile
			projectile = BlueTankProjectileSprite.createProjectile(this);
		} catch (Exception e) {
			e.printStackTrace();
		}
	} // doFireCannon()

	/**
	 * Called every cycle to allow dynamic updating of the sprites frameset
	 */
	public void update() {
		// update tank image based on whether firing, moving or destroyed
		if (cannonSnd.getState() == Player.STARTED
				&& (cannonSnd.getMediaTime() < 250000) && !firingImage) {
			// animate firing cannon
			setImage(tankFireImage, 32, 32);
			firingImage = true;
		} else if (firingImage) {
			// restore normal tank image
			setImage(tankImage, 32, 32);
			firingImage = false;
		} else if (armorPoints == 0) {
			// animate destroyed tank
			switch (tankFrameLookup[cannonDirection]) {
			case 0:
				setImage(BlueTankImageHolder.getDestroyedTankOne(), 32, 32);
				break;
			case 1:
				setImage(BlueTankImageHolder.getDestroyedTankTwo(), 32, 32);
				break;
			case 2:
				setImage(BlueTankImageHolder.getDestroyedTankThree(), 32, 32);
				break;
			}
			setFrame(random.nextInt(3));
		}
		// update projectile fired from this tank, if exists
		if (projectile != null) {
			projectile.update();
		}
	} // update()

	/**
	 * Start two-phase commit for rotating the tank
	 * 
	 * @param delta,
	 *            positive values rotate the tank clockwise while negative
	 *            values make it rotate counter-clockwise
	 */
	public void tryRotate(int delta) {
		if (rotateReady) {
			rotateReady = false;
			btMsgHndlr.sendTankRotateCommitRequest(delta, System
					.currentTimeMillis());
		}
	} // beginRotate()

	/**
	 * Called on two-phase commit success for rotating the tank
	 * 
	 * @param delta,
	 *            positive values rotate the tank clockwise while negative
	 *            values make it rotate counter-clockwise
	 */
	public void doRotate(int delta, long time) {
		// Wait for target commit time
		while (System.currentTimeMillis() < time) {
			Thread.yield();
		}
		// adjust direction
		cannonDirection += delta;
		// overflow brings us back to starting position
		if (cannonDirection < 0)
			cannonDirection += 16;
		if (cannonDirection > 15)
			cannonDirection %= 16;
		// ownerTank transform and frame based on facing direction
		setFrame(tankFrameLookup[cannonDirection]);
		setTransform(tankTransformLookup[cannonDirection]);
		// save for negating move in case of collision
		lastMoveDelta = delta;
		lastMoveWasRotate = true;
		// ready for another rotate transaction
		rotateReady = true;
	} // rotateInPlace()

	/**
	 * Start two-phase commit for driving the tank
	 * 
	 * @param delta,
	 *            positive values drive tank forward in the direction of it's
	 *            cannon, while negative values make it drive backward
	 */
	public void tryDrive(int delta) {
		if (driveReady) {
			driveReady = false;
			btMsgHndlr.sendTankDriveCommitRequest(delta, System
					.currentTimeMillis());
		}
	} // tryDrive()

	/**
	 * This method is used to move the tank forward *and* backward.
	 * 
	 * @param delta,
	 *            positive values drive tank forward in the direction of it's
	 *            cannon, while negative values make it drive backward
	 */
	public void doDrive(int delta, long time) {
		// disallow movement if we're destroyed
		if (armorPoints == 0) {
			return;
		}
		// Wait for target commit time
		while (System.currentTimeMillis() < time) {
			Thread.yield();
		}
		// move along the current vector using traversal instance
		btVectorMove.vectorMove(cannonDirection, delta);
		checkCollisions();
		//
		lastMoveDelta = delta;
		lastMoveWasRotate = false;
		// ready for another drive transaction
		driveReady = true;
	} // doDrive()

	/**
	 * Undo the last tank movement by invoking the forward/rotate method
	 * negating the delta
	 */
	public void negateMove() {
		if (lastMoveWasRotate)
			doRotate(-lastMoveDelta, System.currentTimeMillis());
		else
			doDrive(-lastMoveDelta, System.currentTimeMillis());
	} // negateMove()

	/**
	 * If tank collides with wall or other tank, play a sound an negate the
	 * forward/backward/rotate movement
	 */
	public void checkCollisions() {
		// check for collision with impassable terrain
		TiledLayer wall = btCanvas.getImpassableTerrain();
		// check for collision with other tank
		BlueTankVehicleSprite tank = btCanvas.getPeerTank();
		if (this == tank) {
			tank = btCanvas.getSelfTank();
		}
		if (collidesWith(wall, true) || collidesWith(tank, true)) {
			playTankArmorDentSound();
			negateMove();
		}
	} // checkCollisions()

	/**
	 * This method is called by the BlueTankProjectileSprite class when it
	 * detects a collision with this BlueTankSprite instance *and* the instance
	 * is the target (not firing) tank.
	 */
	public void handleWeaponCollision(boolean notify) {
		// deduct armor points
		playTankArmorDentSound();
		//
		if (armorPoints > 0) {
			armorPoints -= IBlueTankSprite.CANNON_COLLISION_ARMOR_POINTS;
			// if no more armor points after hit, play destroyed sound
			if (armorPoints == 0) {
				try {
					Player player = BlueTankSoundHolder
							.getTankDestroyedPlayer();
					player.start();
				} catch (MediaException e) {
					e.printStackTrace();
				} // try
			} // if no armor
		} // if armor left
		//
	} // handleCannonCollision()

	/**
	 * Gets and plays the tank armor impact sound
	 */
	private void playTankArmorDentSound() {
		// play collision sound
		try {
			Player player = BlueTankSoundHolder.getTankArmorDentPlayer();
			if (player.getState() == Player.STARTED) {
				player.stop();
				player.setMediaTime(0);
			}
			player.start();
		} catch (MediaException e) {
			e.printStackTrace();
		}
	} // playTankArmorDentSound()

	/**
	 * Convenience method for creating instances of this class
	 */
	public static BlueTankVehicleSprite createTank(
			BlueTankGameContainer parent, Image tankImage, int rgb, String name)
			throws IOException {
		return new BlueTankVehicleSprite(tankImage, rgb, name, 32, 32, parent);
	} // createGreenTank()

	/**
	 * Getter for current number of armor points
	 */
	public int getArmorPoints() {
		return armorPoints;
	} // getArmorPoints()

	/**
	 * Getter for the tank color
	 */
	public int getTankRGB() {
		return tankRGB;
	} // getTankRGB()

	/**
	 */
	public String getTankName() {
		return tankName;
	} // getTankName()

	/**
	 */
	public BlueTankGameCanvas getBtCanvas() {
		BlueTankGameCanvas btCanvas = null;
		if (container != null) {
			btCanvas = container.getBtCanvas();
		}
		return btCanvas;
	} // getBtCanvas()

	/**
	 * Getter for the current cannon direction
	 */
	public int getCannonDirection() {
		return cannonDirection;
	} // getCannonDirection()

	/**
	 * Setter for the current cannon direction
	 */
	public void setCannonDirection(int cannonDirection) {
		this.cannonDirection = cannonDirection;
	} // setCannonDirection

	/**
	 * Utililty method for inidicating firing sequence complete
	 */
	public void clearProjectile() {
		projectile = null;
		cannonReady = true;
	} // clearProjectile()

} // BlueTankVehicleSprite()
